using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using TrueCode.Core.Aspects;
using TrueCode.DependencyManager.BuildActions.Injection;


namespace TrueCode.DependencyManager.Services.InjectionPoints
{
  public class MethodByNameProvider : IMethodProvider
  {
    private readonly IMethod[] _methods;

    #region .ctors
    public MethodByNameProvider( params string[] methodNames ) : this( ConstructMethodsFromNames( methodNames ) )
    {
    }

    public MethodByNameProvider( params IMethod[] methods )
    {
      if( methods == null || methods.Length == 0 ) throw new ArgumentNullException( "methods" );

      _methods = methods;
    }
    #endregion

    #region IMethodsProvider Members
    public IEnumerable< MethodInfo > GetMethodsOf( Type type )
    {
      InsertionSort( _methods );

      var methodsInfo = new MethodInfo[_methods.Length];
      for( var i = 0; i < methodsInfo.Length; i++ )
      {
        methodsInfo[ i ] = _methods[ i ].GetMethod( type );
      }

      #region Assert that there are no duplicated methods in result collection
      var unique = methodsInfo.Distinct();
      if( unique.Count() != methodsInfo.Length )
      {
        var exception = new InvalidOperationException( "Some methods duplicated" );
        var i = 0;

        foreach( var duplicate in Subtract( methodsInfo, unique ) )
          exception.Data.Add( "Duplicate" + i++, duplicate );
        exception.Data.Add( "Type", type );
        throw exception;
      }
      #endregion

      return methodsInfo;
    }
    #endregion

    public static IEnumerable< T > Subtract< T >( IEnumerable< T > enumerable, IEnumerable< T > subtrahend )
    {
      var minuend = new List< T >( enumerable );
      foreach( var item in subtrahend )
        minuend.Remove( item );

      return minuend;
    }

    #region Private
    private static void InsertionSort( IMethod[] methods )
    {
      for( var j = 1; j < methods.Length; j++ )
      {
        var key = methods[ j ];

        int i;
        for( i = j - 1; i >= 0 && methods[ i ].CompareTo( key ) > 0; i-- )
        {
          methods[ i + 1 ] = methods[ i ];
        }
        methods[ i + 1 ] = key;
      }
    }

    private static IMethod[] ConstructMethodsFromNames( [NotNull] ICollection< string > methodNames )
    {
      if( methodNames == null || methodNames.Count == 0 ) throw new ArgumentNullException( "methodNames" );
      return methodNames.Select( mn => new NamedMethod( mn ) ).ToArray();
    }
    #endregion
  }
}